<?php
declare(strict_types=1);

namespace Zms\Guard;

use Hyperf\Context\ApplicationContext;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Zms\Guard\Drives\DrivesInterface;
use Zms\Guard\Drives\SessionDrives;
use Hyperf\DbConnection\Model\Model;

use Hyperf\HttpServer\Contract\RequestInterface;
use Zms\Exception\{
    NotLoggedException, ExpireLoggedInException
};
use Zms\Unit\Str;
use Hyperf\Context\Context;

class Guard
{
    private Config $config;

    public function __construct(Config $config)
    {
        $this->config = $config;
    }


    /**
     * 获取用户ID
     * @param string $token
     * @return int
     * @throws NotLoggedException|ExpireLoggedInException
     */
    public function uid(string $token = ''): int
    {
        $token = $this->getSecretToken($token);
        if (!$token) {
            throw new NotLoggedException("请先登录");
        }
        $uid = $this->storage()->get($token);
        if (is_int($uid)) {
            return $uid;
        }
        throw new ExpireLoggedInException("登录已失效");
    }

    /**
     * 获取会员实例
     * @param string $token
     * @return ModelInterface|Model
     * @throws ExpireLoggedInException
     * @throws NotLoggedException
     */
    public function user(string $token = ''): ModelInterface|Model
    {
        if (Context::has(__CLASS__)) {
            return Context::get(__CLASS__);
        }
        $uid = $this->uid($token);
        $guard = $this->config->getGuard($uid);
        if ($guard) {
            Context::set(__CLASS__, $guard);
            return $guard;
        }
        $this->logout($token);
        throw new ExpireLoggedInException("登录已失效");
    }


    /**
     * 获取登录信息
     * @param string $token
     * @return array
     * @throws NotLoggedException|ExpireLoggedInException
     */
    public function UserInfo(string $token = ''): array
    {
        $uid = $this->uid($token);
        $UserToken = $this->genUserToken($uid);
        $user = $this->storage()->get($UserToken);
        if (is_array($user) && isset($user['uid']) && isset($user['UserInfo'])) {
            return $user['UserInfo'];
        }
        $guard = $this->config->getGuard($uid);
        $this->saveGuardData($guard);
        return $guard->userInfo();
    }


    /**
     * 登录
     * @param ModelInterface $user
     * @return string
     */
    public function login(ModelInterface $user): string
    {
        $this->saveGuardData($user);

        $token = $this->getLoginToken();

        $this->storage()->set($token, $user->getId(), $this->config->getTtl());

        return $token;
    }

    /**
     * 退出登录
     * @param string $token
     * @return void
     */
    public function logout(string $token = ''): void
    {
        $token = $this->getSecretToken($token);
        if ($token) {
            $this->storage()->delete($token);
        }
    }


    /**
     * 刷新登录信息
     * @param int $uid
     * @return bool
     */
    public function refresh(int $uid): bool
    {
        $user = $this->config->getGuard($uid);
        if ($user) {
            $this->saveGuardData($user);
            return true;
        }
        return false;
    }

    /**
     * 游客模式 false代表已登录 true代表未登录
     * @param string $token
     * @return bool
     */
    public function tourist(string $token = ''): bool
    {
        $token = $this->getSecretToken($token);
        if (!$token) {
            return true;
        }
        $uid = $this->storage()->get($token);
        return !((is_int($uid) && $uid));
    }


    /**
     * 获取登录用户token名称
     * @param int $uid
     * @return string
     */
    private function genUserToken(int $uid): string
    {
        return Str::md5_16($this->config->getKey() . '_uid_' . $uid);
    }


    /**
     * 获取登录token
     * @return string
     */
    private function getLoginToken(): string
    {
        if ($this->config->getSecret() === 'session' || $this->config->getDrive() === SessionDrives::class) {
            return Str::md5_16($this->config->getSecretKey());
        }
        return Str::md5_16(time(), $this->config->getKey(), rand(10000, 99999));
    }


    /**
     * 储存登录凭据
     * @param ModelInterface $user
     * @return void
     */
    private function saveGuardData(ModelInterface $user): void
    {
        $UserToken = $this->genUserToken($user->getId());
        $data = [
            'uid' => $user->getId(),
            'UserInfo' => $user->userInfo()
        ];
        $this->storage()->set($UserToken, $data);
    }


    /**
     * 获取储存驱动
     * @return DrivesInterface
     */
    private function storage(): DrivesInterface
    {
        return ApplicationContext::getContainer()->get($this->config->getDrive());
    }

    /**
     * 获取登录凭据
     * @param string $token
     * @return string
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    public function getSecretToken(string $token = ''): string
    {
        if (is_string($token) && strlen($token) === 16) {
            return $token;
        }
        $request = ApplicationContext::getContainer()->get(RequestInterface::class);
        $key = $this->config->getSecretKey();
        $token = '';
        switch ($this->config->getSecret()) {
            case $this->config::SECRET_POST:
                $token = $request->post($key);
                break;
            case $this->config::SECRET_QUERY;
                $token = $request->query($key);
                break;
            case $this->config::SECRET_SESSION;
                return Str::md5_16($key);
            default:
                if ($request->hasHeader($key)) {
                    $token = $request->getHeader($key)[0] ?? '';
                }
        }
        return (is_string($token) && strlen($token) === 16) ? $token : '';
    }
}